堆空间:动态分配的空间;
栈空间:非静态局部变量空间,函数参数,函数内部定义的变量,以及返回的地址值;
全局和静态数据区:全局变量、静态局部变量,常量;
数据段,用于存储字符串字面量。
代码区:存放代码的区域;
对于栈空间,VC6一般就分配1M左右;
变量名只是隐式地声明了变量值存储的地址,而它的指针则是显式地声明了变量值存储的地址。
C将数组名视为指针,即将数组名解释为其第一个元素的地址,如apples = &apples[0];
符号常量:#define PI 3.14159
const常变量:const int a=3;(必须写成一行,不能分开写)
typedef short int wchar_t;
计算机的内存(RAM)由数百万个顺序存储位置组成,每个位置都有唯一的地址。计算机的内存地址范围从0开始至最大值(取决于内存容量,可访问的最大值取决于计算机的数据总线的数量,如果是32位,则是到2^32,4G)。
运行计算机时,操作系统要使用一些内存。运行程序时,程序的代码(执行程序中不同任务的机器语言指令)和数据(该程序使用的信息)也要使用一些内存。
在C程序中声明一个变量时,编译器会预留一个内存位置来储存变量。此位置有唯一的地址。编译器把该地址与变量名相关联。当程序使用该变量名时,将自动访问正确的内存位置。
要理解变量作用域,首先要理解结构化编程的思路。结构化编程把程序分成若干独立的函数,每个函数都执行特殊的任务。这里的关键是函数独立。为了真正让函数独立,每个函数的变量都不能受其他函数代码的影响。只有隔离每个函数数据,才能确保函数在完成自身任务时不会被其他函数破坏。在函数中定义变量,便可“隐藏”这些变量,让程序的其他部分无法访问它们。
然而,并非所有情况都要在函数间完全隔离所有的数据。程序员通过指定变量的作用域能很好地控制数据隔离的程序。
任何变量都有一个指定的存储类别,用于决定变量的作用域(在程序中何处可见)和生命期(变量在内存中的存活时间)。
对于结构化编程,正确使用存储类别非常重要。在函数中使用局部变量,提高了函数间的独立性。尽量使用自动存储类别的变量,除非有特殊原因需要使用外部或静态变量。
既然外部变量在程序中的任何地方都可用,为何不将所有的变量都声明为外部变量?
随着程序越来越大,包含的变量也越来越多。外部变量在程序运行期间会一直占用内存,而自动变量只在执行它所在的函数时占用内存。因此,使用局部变量节约内存空间。然而,更重要地是,使用局部变量能减少程序不同部分不必要的交互,从而减少了程序的bug,同时也遵循了结构化编程的原则。
常量是不可以改变的数据,常量按照数据类型主要分为整型常量、浮点型常量、字符型常量、字符串常量、转义字符常量、地址常量等6种。
在C中,字面量(右式)除了字符串字面量以外,都只作代码处理,并不单独分配内存,而字符串字面量要分配内存,并保存到内存的数据段、栈区或堆区。
char *s1, *s2, *s3 = “abcde”; char ch[] = “fff”; s1 = ch; s2 = new char[10]; strcpy(s2, “fgh”);
这样保存的好处是当有多个地方使用同一字符串常量时,可以直接引用。
变量,顾名思义就是在程序运行中,可以被改变的量。变量在定义后为程序提供了一个有名字的内存区域,编程者可以通过程序对它进行读写和处理。变量值的改变是通过赋值操作进行的。变量的基本使用示例代码如下:
double pi = 3.14; //定义double型变量pi pi = 3.1415; pi = 3.1415926; //可以通过赋值不断改变pi的值 scanf( "%lf", &pi );
常量和变量都是在程序中需要经常使用到的,特别是变量,它们有着不同的特征,常量和变量的主要区别如下。
(1)常量的值不可以修改,任何尝试修改常量的操作都会导致编译错误。而变量可以通过赋值来改变值。
(2)常量定义以后就不可以修改,所以常量在定义时必须初始化。变量可以在定义时暂不进行初始化。常量初始化的时候必须直接复制常量初始化的示例代码如下:
const char a = "test" //正确 char p; p="test"; const test = p; //错误,常量必须直接赋值
(3)常量值的地址不允许赋给非常量指针。
(4)常量在编译的时候,可以以立即数形式编译进指令,比起使用内存的变量执行效率更高。
(5)常量本身没有地址属性(除字符串常量等),而变量有地址属性。所以常量只能用做右值,而变量左值、右值都可以。
常量是不可以改变值的量,变量是可以改变值的量,常量在定义时必须初始化,变量可以在定义时不初始化。常量不可以寻址,它的地址不允许赋给非常量指针,变量可以寻址。常量有相对较高的编译执行效率。
操作符优先级决定了表达式运算的次序,操作符优先级越高,则运算次序越靠前。
在所有的优先级中,有3个是从右至左结合的,分别是单目运算符、条件运算符、赋值运算符。其他的操作符都是从左至右结合。
逻辑与和逻辑或操作符在使用时,总是先计算它的左操作数,然后再计算右操作数。只有在靠左操作数的值无法确定该逻辑表达式的结果时,才会去求解右操作数。这种求值方式也被称为“短路求值(short-circuit evaluation)”。
前自增操作使其操作数加1,操作结果是修改后的值。同样,前自减操作使其操作数的值减1。这两种操作符的后置形式同样对其操作数执行加1(或减1)的操作,但后置形式在操作后表达式的结果是操作数原来的、未修改的值。也就是说,前自增自减操作返回左值,也就是对象本身,而后自增自减操作返回的是右值。
int m,n,i=3,j=8; m=++i;//i=i+1;m=i; n=j++;//n=j;j=j+1;
i,j都有自增1,而对于m,n的值,可按就近原则去理解,m=++i,先要加再赋值,n=j++,先赋值再加。
按表达式的结合性,赋值运算符是右结合,从右边开始计算,但后置却明显违背了这一规则,编译器的特殊处理是,使用一临时变量达到右结合:int temp=j;j=j+1;n=temp;
变量的自增自减是修改变量的值,指针的自增自减是修改指针的指向地址。
变量有两个值:一个是内存块的首地址,一个是内存块的一串二进制码按数据类型的编码解码规则解码出来的数据值。
如果数据值也是一个地址值,那这个变量就是指针变量。
引用是一个特殊的常量指针,在C++中使用。
C中变量为程序提供了可以操作的有名字的存储区。变量的主要意义有以下两点。
变量代表分配了一块存储区;
变量代表这个存储区的名称。
左值可以出现在赋值语句的左边或右边,也就是说左值可以当右值使用。右值只能出现在赋值的右边,不能出现在赋值语句的左边。左值表示程序中必须有一个特定的名字引用到这个值。右值表示程序中没有一个特定的名字引用到这个值。
变量是左值,因此可以出现在赋值语句的左边。数字字面值是右值,因此不能被赋值。实际上,左值是一个存储地址,也就是一块内存存储数据所要操作的地址。而右值是一个具体的数据或者数值,也就是该内存存储的数据内容。只有左值和右值都是单一变量的时候二者才可以相互交换位置,因为变量具有固定的内存地址。
左值和右值的示例代码如下:
int a = 1; // 变量a是一个左值 char str[] = "hello, world"; // 数组成员str[i]是左值 "hello, world"; // 这个表达式是一个数据内容,它是一个右值 string("hello, world"); // 这也是一个右值
注意:有些操作符,例如赋值,要求其中的一个操作数必须是左值。结果,可以使用左值的上下文比右值更广。左值出现的上下文决定了左值是如何使用的。
变量是左值,可以出现在赋值语句的左边。数字字面值是右值,不能被赋值。
如果变量在定义的时候没有被初始化,它的值将是不确定的,这也是程序员在使用变量时需要考虑到的一点,不确定的值有可能导致程序出现错误。一般来说,编程者为了保证程序的稳定性,尽量做到将所有的变量都进行初始化。
声明用于向程序表明变量的类型和名称。定义也是一种声明,当定义变量时编程者声明了它的类型和名称。也可以通过使用extern关键字声明变量名但是不定义它。不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern,示例代码如下。
extern int a; //声明但是未定义a
int b; //定义b
注意:extern声明不是定义,也不会分配存储空间。它只是说明变量定义在程序的其他地方(只是一个名字的引入)。含有初始化的extern声明被当做是定义,程序中变量可以声明多次,但只能定义一次。
变量的定义用于为变量分配存储空间,还可以给变量初始化。在一个程序中,变量有且只有一个定义,定义变量的示例代码如下。
int a; int b; int c; //定义3个整型变量a,b,c int d,e,f; //同时定义3个整型变量 int g = 10; //定义变量g并且初始化
对于C/C++语言,因为多是开发操作系统的底层技术,所以内存分配非常关键。下面从多方面解释内存分配方式:
(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如,全局变量和static变量。
(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
(3)从堆上分配,亦称动态内存分配。程序在运行的时候使用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
链接性linkage,链接器linker就是用来链接库和多文件的,所以链接性是指是否允许其可以被其它文件访问。
在C语言中,每个变量有两个属性:
1 类型:变量所存储的数据类型
2 存储类型:变量所存储的区域:
标准的变量定义:
存储类型 数据类型 变量名;
存储类型:
1 自动变量:auto
2 寄存器变量:register
3 外部变量:extern
4 静态变量:static
字符串常量存储在内存中,赋值给一个字符指针的字符串变量存储在一个称为数据段的内存区域里(其他字面量只是代码的一部分),new给一个指针的字符串常量存储在堆中,赋值给一个字符数组的字符串常量存储在栈中。
字符串作为字符数组传递时不需要指定长度。因为字符串操作的结束是依据‘\0’
指针指向的内容还是一个指针,称为多级指针
如有定义:char *string[10];
string是一个数组,数组元素可以通过指针来访问。如果p是指向数组string的某一个元素,那么p指向的内容是一个指向字符的指针,因此p就是一个多级指针。Name也是一个多级指针,不过是一个常指针
等价于a[i][j]的表达式 *(a[i] + j) (*(a + i))[j] *((*(a + i)) + j) *(&a[0][0] + 5 * i + j)
全局变量和局部变量的主要区别是什么?使用全局变量有什么好处?有什么坏处?
局部变量是函数内部或语句块中定义的变量,当函数调用时局部变量被生成,函数执行结束时局部变量被消亡。全局变量是在所有函数外定义的变量它可以一直生存到整个程序执行结束。所有定义在该变量后面的函数都能使用该全局变量。使用全局变量可以加强函数之间的联系。函数之间的信息交互不用通过参数传递。但全局变量也破坏的函数的独立性。用同样参数对同一个函数的多次调用可能因为执行时全局变量值不一样而导致函数执行的结果不同。
变量定义和变量声明有什么区别?
变量定义和变量声明的最主要的区别在于有没有分配空间。变量定义要为所定义的变量分配空间,而变量声明仅指出本源文件中的程序用到了某个变量(仅指使用关键字extern),该变量的类型是什么。至于该变量的定义可能出现在其他源文件中,也可能出现在本源文件中尚未编译到的部分。
为什么不同的函数中可以有同名的局部变量?为什么这些同名的变量不会产生二义性?
局部变量的生命周期是在对应的函数的执行期间。函数调用时,系统会分配一块内存空间(称为一个帧),所有的局部变量都存储在这块空间中。当函数执行结束时,系统回收这块空间,这些变量就消失了。每个函数有自己的存储局部变量的空间,每个函数只能访问自己的帧及全局变量。所以不同的函数可以有同样的局部变量名,而不会有二义性。
静态的局部变量和普通的局部变量有什么不同?
普通的局部变量随函数的执行而生成,函数执行的结束而消亡。静态的局部变量在该函数第一次执行时生成,到整个程序执行结束时消亡。当再次执行该函数时,其他的局部变量又重新被生成了,而静态局部变量不重新生成。当函数用到静态的局部变量时,还是到第一次为该静态局部变量分配的空间中进行操作。这样,函数上一次执行时的某些信息可以被用在下一次函数执行中。
如何让一个全局变量或全局函数成为某一源文件独享的全局变量或函数?
将该全局变量或全局函数设为静态的。
如何引用同一个project中的另一个源文件中的全局变量?
用外部变量声明。如果源文件A中定义了一个全局变量x,源文件B也要用这个x,那么在源文件B中可以写一个外部变量声明:extern x;
什么是模块的内部状态?内部状态是怎样保存的?
模块的内部状态就是模块内多个函数需要共享的信息,这些信息与其他模块中的函数无关。内部状态通常被表示为源文件中的全局变量,以方便模块中的函数共享。
定义变量时没有赋初值,然后直接引用(用作右值)该变量是危险的!!!
一个标识符能被存取的程序部分,称为标识符的作用域。
标识符的作用域与程序块有关。所谓的程序块是带有声明的复合语句。
声明一个不在本模块作用范围内的全局变量。如:
extern int num;
num为某一个全局变量。
用途:
在某函数中引用了一个声明在本函数后的全局变量时,需要在函数内用extern声明此全局变量。
当一个程序有多个源文件组成时,用extern可引用另一文件中的全局变量。
多级指针的应用:可以用指向指针的指针访问指针数组的元素。如int** p; 解引用*p的内容还是一个地址,**p才是具体值;
标识符的存储类别决定了标识符在内存中存在的时间。作用域是指标识符在程序中可以被引用的范围。
malloc()函数的功能是在内存的动态存储区中分配size个字节的连续空间,它的返回值指向所分配的那一段空间的起始地址,若分配失败,则返回一个空指针(0)。
void *malloc(unsigned int size);
void *calloc(unsigned int n, unsigned int size);
*calloc()的功能是在内存的动态存储区中分配n个长度为size个字节的连续空间,它的返回值是指向所分配空间的起始地址,若分配失败,则返回一个空指针(0)。
当今的操作系统都会给应用程序的每一个进程分配独立的“虚拟地址空间”。
程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到合适的物理内存地址上。这样一来,只要操作系统处理好虚拟地址到物理地址的映射关系,就可以保证不同的程序最终访问不同的区域,从而达到内存地址空间的隔离。
物理地址存在于物理内存中,同理,虚拟地址存在于虚拟地址空间中。要进行数据访问,必须由操作系统将虚拟地址转化为物理地址。我们在C语言和C++中看到的地址都是虚拟地址。用户是看不到物理地址的,物理地址由操作系统统一管理。
C语言中malloc()大体的实现是,从操作系统一次性地取得比较大的内存,然后将这些内存“零售”给应用程序。
计算机系统中存在着几种不同层次的内存管理层面,其中每层都可能覆盖原先的(较低的)层面。
首先是操作系统内核提供了最为基础的内存分配服务。然后是编译器的默认运行库也会建立起它自己的内存分配服务,如C++中的operator new和C中的mallowc,这一层是建立在操作系统的本地分配服务基础上的。
根据基本数据类型声明的变量可以告诉编译器以下信息:
1 需要的内存空间;
2 取值的范围;
3 可以执行的操作(如可以使用什么运算符,使用运算符的规则及达到的效果);
内存中存储数据,可以有多种模型来帮助理解数据的组织。
为何使用指针?
1 管理堆中的数据;
2 访问类的成员数据和成员函数;
3 按引用将变量传递给函数;
老式编译器当分配堆内内存失败时,会返回NULL指针,而较新的编译器都会引发异常。
使用完分配的内存区域后,必须对指针调用delete,将内存归还给堆。别忘了,指针本身为局部变量,这不同于它指向的内存;当声明指针的函数返回时,指针将不再在作用域中,因此被丢弃。然而,使用new运算符分配的内存不会自动释放,这些内存将不可用,这被称为内存泄露,因为在程序结束前,内存不会归还给堆,就像内存从计算机中漏掉了。
int *pBuffer = NULL
NULL是一个预处理器宏,将被转换为0(int值)或0L(long值)。
常量类型:
1 const声明的常量;
2 constxpr声明的常量表达式;
3 enum声明的枚举常量;
4 #define定义的常量(已摒弃,不推荐)
5 字面常量
malloc的全称是memory allocation,中文叫动态内存分配,用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址,当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态的分配内存。
void* 类型表示未确定类型的指针。C、C++规定,void* 类型可以通过类型转换强制转换为任何其它类型的指针。
类型未确定,即无法确定对应内存空间的长度和解码方案,强制类型转换,即重新确定长度和解码方案。。
一般需和free函数配对使用。
malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。
指针类型的值用来表示某个量在内存储器中的地址;
整数:正数以原码保存,负数与补码存储;
实型:在内存中占4个字节,是按照指数形式存储的,分为小数部分与指数部分。
小数部分:用二进制表示;
指数部分:用2的幂次来表示;
字符型数据:按其对应的ASCII码来存储,而所有的ASCII代码值在内存中也是以二进制形式存放的,与整形在内存中存储的方式很相似,所以两类之间的转换也比较方便;
左结合:逻辑非!、按位取反~、负号-、自加1++、自减1--、取地址&、指针所指内容*、函数说明type、长度计算sizeof、条件运算符?:、联合操作=+=-=等;
符号&,当其操作数是一目还是二目时,其操作是不同的(返回操作数的地址);当其操作数时二目时,操作数类型不同时,进行的也是不同的操作(如按位与、逻辑与);
&和|对操作数进行求值运算,&&和||只是判断逻辑关系;&&和||在判断左操作数就能确定结果的情况下就不再对右操作数求值;
在编程中,左值位于赋值运算符的左侧,右值位于赋值运算符的右侧。
变量和常量都是按其相关的类型存储在内存空间内的,不过变量是可以寻址的而常量不可以寻址。对于每一个变量都有两个相关值:地址值和数据值:
1 数据值:存储在其内存地址中的数据,也称为变量的右值;
2 地址值:存储数据值的那块内存的地址,也称为变量的左值;
如以下代码:
int a = 3;
a = a + 5;
在a=a+5中,变量a同时出现在赋值操作符的左边和右边。右边的实例被读取,与其相关联的内存中的数据值被读出,左边的a用作写入。表达式a+5的值将要存储在a的位置值所指向的内存区中,原来的数据值会被覆盖。即在赋值操作符右边的a+5为右值(数据值,供读入)。达边的a为右值(地址值,供写入)。
malloc()函数接受一个参数:所需的内存字节数。malloc()函数会找到合适的空闲内存块,这样的内存块是匿名的。也就是说,malloc()分配内存,但是不会为其赋名。然而,它确实返回动态分配内存块的首字节地址。因此,可以把该地址赋给一个指针变量,并使用这个指针变量访问这块内存。
malloc()函数返回的是一个void通用指针,可以通过强制类型转换来转换为特定的指针类型。
calloc()函数接受两个参数,第一个参数是所需的存储单元数量,第二个参数是存储单元的大小(以字节为单位)。另外,callo()函数会把块中的所有位都设置为0.
c语言中的数据和代码是需要存放才可以使用的,C语言用变量来存储数据,用函数来定义一段可以重复使用的代码,它们最终都要放到内存中才能供 CPU 使用。
数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存到底存储的是数据还是代码。当程序被加载到内存后,操作系统会给不同的内存块指定不同的权限,拥有读取和执行权限的内存块就是代码,而拥有读取和写入权限(也可能只有读取权限)的内存块就是数据。
CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。
然而指针也是一种变量,他里面装的就是所指数据或者代码的地址。所以它可以指变量,也可以指函数。
两个指针指向同一个位置:
p2 = p1;
或
p2 = &*p1;
下面到底哪个是数组指针,哪个是指针数组呢:
int *p1[10];
int (*p2)[10];
“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。至于p2 就更好理解了,在这里“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。
Node *head 是定义一个指向Node结构体变量的指针。
Node *&head实际上应该写成 (Node *)&head,其中&head是取变量head的地址,然后将此地址再转换为Node结构体类型的地址。在此表达式中,head的数据类型不一定是Node结构体数据的数据,它可以是任何类型的,与Node *无关。它仅仅用于取它的首地址。
可以操作头节点,所以head是引用。
指针虽然可以突破区块的限制,但如果指针指向的是一个复合数据类型,如果想更改复合数据类型内的元素的值时,需要进一步去引用。也就是p* &head的写法。
常量指针也是对数据的一种保护,但实质保护的不是指针本身,还是指针指向的数据不能通过指针去修改。
指针有性能优势,当做为函数参数时,在函数调用时,并不需要另行开辟内存空间,也就不需要另行对参数进行初始化或赋值,这是会浪费时间的,与此同时,也节省和空间。使用指针也是对局部空间的一种突破。另外,可以突破静态数组事先需要确定大小的限制,使用指针申请堆内存可以在运行时根据需要申请堆内存变量数量。
所有变量的作用域都开始于变量的声明处,换句话说,变量必须先声明再使用。
变量的操作:
1 declaration
2 assignment
2.1 initialization
2.2 modification
声明指针为什么需要声明类型,原因之一就是当指针运算产生偏转或读取数据时,它知道按不同的类型需要读取多少内存或偏转多少位置?
类型包含内存空间、解码的方式以及可以使用的操作。
变量:类型、名称、地址、值
指针变量:类型、名称、地址、值(地址)
地址就是变量在计算机内部的名称。
在C中,可以用&运算符访问变量的地址;
可以用*来获得储存在地址上的值;
普通变量把值作为基本量,把地址作为通过&运算符获得的派生量;
指针变量把地址作为基本量,把值作为通过*运算符获得的派生量;
typedef比#define更灵活。typedef由编译器解释,只能用于类型,不能用于值。能提高程序的移植性。
typedef用于将复合类型声明进行简写。
char * string; typedef char * STRING; STRING是一个类型,该类型是指向char的指针; STRING name, sign; 相当于 char * name, * sign; typedef {double x; double y;} rect; rect r1 = {3.0, 6.0}; rect r2; 以上代码可以翻译成: typedef {double x; double y;} r1 = {3.0,6.0}; typedef {double x; double y;} r2; r2 = r1;
使用typedef时记住,typedef并没有创建新的类型,它只是为某个已存在的类型增加了一个方便使用的标签。可以把它理解为一个可移植数据的工具。因为C是强类型语言。
关键字extern声明,表明该变量或函数已定义在别处。
extern:用于声明全局变量,声明的变量再此声明后或其它文件中有声明并定义。
没有初始化的值可能是垃圾值或0值或void值;
与变量相关的概念
1 名字,编译后就是地址,运行时操作系统再关连的是虚拟地址;
2 地址
3 值、左值、右值
普通变量把值作为基本量,把地址作为通过&运算符获得的派生量;
指针变量把地址作为基本量,把值作为通过*运算符获得的派生量;
4 类型,基本类型、复合类型
5 值的初始化
6 存续期
7 作用域
8 文件链接
char *p = “test”;
p[2] = 'p';//不允许
因为“test”做为字面量存储在BBS内存区,返回一个常量指针给p,常量不能被修改,所以不能再被赋值的,编译器会自动把它定义为常量指针
左值有可修改的左值,不可修改左值;
标识符的作用域scope和链接表明了程序的哪些部分可以使用它。取决于标识符所在的位置以及关键字static.
存储期表明了数据在内存中保留了多少时间。
存储期:程序执行期、函数执行期;
C变量的作用域有块作用域、函数作用域、函数原型作用域、文件作用域。
block scope、function scope、function prototype scope、file scope、
如果你想要将将参数传递给一个函数而不复制它们,又或者希望函数对参数的修改能够对调用者可见。
作用域和链接描述了标识符的可见性。存储期描述了通过这些标识符访问的对象的生存期。C对象有4种存储期:静态存储期、线程存储期、自动存留期、动态分配存储期。
对于文件作用域变量,关键字static表明了其链接属性,而非存储期。
malloc()返回一个指向指定字节数内存块的指针。这块内存被free()函数释放后便可重复使用,free()函数以该内存块的地址作为参数。
多处声明,一处定义;
extern int i; //声明外部整型变量
char * f(); //声明函数
int j; //声明和定义变量
int k = 11 //声明和定义变量
定义整型常量,前面是0时,表示是8进制,前面是0x时,表示16进制,包括0-9,a,b,c,d,e,f共15个符号。后面可以跟u,l或ul,u表示无符号整数,l表示长整形。
指针可以指向基本类型对象,也可以指向自定义类型的对象;
指针作用:在函数内部访问在函数外部定义的大块数据,如数组。
更重要的是能够动态地为变量分配存储空间。
这种功能使程序可以根据实际的输入,调整自己的内存使用量。因为预先不知道将动态创建多少个变量,所以首选方法是使用指针。
变量是一段连续内存单元的首地址的命名,这个首地址也可以赋值给一个指针。
指针的声明和定义与变量有相似性,因为指针变量也是一个变量:
int * i;
i是指针,与变量一样,声明和定义是同时进行的,此时也分配了一个内存空间(所有指针都是一个字长大小的空间)。在初始化之前,此时i中存储的值是一个随机的垃圾值,如果未做初始化即对指针做解引用操作,将引发不可预见的错误。就如果普通变量未做初始化即用之做操作会引发错误一样。
指针主要有三个方面的作用:
1 间接访问;
2 允许在函数间共享内存空间;可以让参数作为返回值(参数一般是作为函数输入的);
3 动态变量, such as dynamic array;the operator: new delete malloc free;
指针的类型同样确定了指针指向的一段连续的内存单元的长度;
当指针执行与整数i的加减运算时,就会引起指针的偏移,偏移的长度是i*数据类型的长度;
变量:直接返回内存单元对应的值,不用考虑其地址;
指针变量:其值是一个地址,解引用返回地址对应的值;
lvalue是一个持续存在的地址。
在访问一个内存单元时,除了使用名字以外,还可以直接使用地址。C++的地址类型支持这种访问方法。C++的地址类型有两种,指针和引用。
NULL是零值的符号化定义;
引用就是一个单元的别名,也就是一个单元会有多于一个的名字。
malloc函数的参数是以字节计的内存大小,所以程序员需要自己计算待分配单元的字节数。
malloc函数的返回值是无类型指针void *,而程序往往要将它转换为合适的指针类型。
new的参数是待分配单元的数目,它自动计算要分配类型的字节数;如q = new double[10];
new能自动返回正确的指针类型,不必对返回指针进行类型转换。
new可以直接初始化,如q = new double(2.2);
Consider an assignment statement.Its purpose to store a value at a memory location. Data object is a general term for a region of data storage that can be used to hold values. The C standard uses just the term object for this concept. One way to identify an object is by using the name of a variable. But, as you will eventually learn, there are other was to identify an object. For example, you could specify an element of an array, a member of a structure, or use a pointer expression that involves the address of the object. C uses the term lvalue to mean any such name or expression that identifies a particular data object. Object refers to the actual data storage, but an lvalue is a label used to indentify, or locate, that storage.
referred to an address in memory;
be used on the left side of an assignment operator; but a const varible can't. so lvalue can be named modifiable lvalue.
rvalue refers to quantities that can be assigned to modifiable lvalues but which are not themselves lvalues.
rvalue can be constants, variables, or any other expression that yields a value,such as a function call.
const variable ,non-modifiable, can be used on the right ise of an expression.
rvalue doesn't represent a specific memory location and you can't assign to it. It's just a temporary value the program calculates, and then discards when it's finished with it.
If a computer has 1 million bytes of memory, its memory locations range from 0 to 999,999. Among other things, memory locations are used for storing the values of variables. Suppose a variable num has the value 36 and this value is stored at memory location 5000. We say the storage address (or, simply, the address) of num is 5000.
The operator &, when applied to a variable, returns the address of the variable. For example, suppose num is stored at address 5000. Then the value of &num is 5000.
The term pointer is used to refer to an address in memory. A pointer variable is one that can hold the address of a memory location.
An abstract data type is one that allows a user to manipulate the data type without any knowledge of how the data type is represented in the computer. In other words, as far as the user is concerned, all he needs to know are the operations that can be performed on the data type. The person who is implementing the data type is free to change its implementation without affecting the users.
对于程序中使用到的常量、变量的类型要事先进行定义才能使用,这是保证程序可靠性的手段之一。早期的一些计算机程序设计语言不要求对变量的类型进行定义,因此,一个变量的类型在程序运行期间是不确定的,这将会降低程序的可靠性。
表达式p++ 是表示将指针p往后移动一个字节吗?
不是。表达式p++ 是表示将指针p往后移动一个元素所占的存储单元(一个或多个字节),而到底是几个字节要由p所指示的元素本身的数据类型所决定。例如:int *p1; char *p2; 此时,p1++表示p1往后移动两个字节,而p2++表示p2往后移动一个字节。
什么是空指针?
空指针与同类型的其它所有指针都不相同, 也与任何对象或函数的指针都不相等。也就是说, 用取地址操作符&永远也不能得到空指针, 同样对 malloc() 的成功调用也不会返回空指针, 除非调用失败, malloc() 才返回空指针。
实际上,空指针表示“未分配”或者“尚未指向任何地方”的指针。它在概念上不同于未初始化的指针。因为,空指针可以确保不指向任何对象或函数,而未初始化指针则可能指向任何不确定的地方。
C语言中的NULL是代表的什么?
NULL是C语言中定义的预处理宏,它一般代表0 或者 ((void *)0),用于表示一个空指针。在C程序中,NULL实际上和0完全等价的。需要注意的是:NULL只能用作指针常量来使用。
定义性声明:需要建立存储空间的(如:int a; )声明。 引用性声明:不需建立存储空间的声明(extern a;)。 声明包括定义,但并非所有的声明都是定义。对“int a;” 而言,它既是声明,又是定义。而对“extern a;” 而言,它是声明而不是定义。指针的5个维度,用四个字来概括,就是:"两己三他"!是不是读起来一点都不顺口,一点都不押韵啊,什么个玩意儿。这"两己三他",展开来说,就是:己址、己值、他值、他址、他型。
C语言中的指针可以分为三类:指向数据对象的指针、指向函数的指针和指向“虚无”的指针(void *类型)。对它们可以进行的运算完全不同。对指向数据对象的指针可以进行*(间接访问)、+、-等运算;指向函数的指针则只有一元*运算,而不存在+、-等运算;至于指向void 类型的指针,*(间接访问)、+、-等几种运算都没有定义。指向数据对象的指针可能勉强而不精确地称为“变量的地址”,其他两种指针都跟变量没有什么关系。
指针:一段存储空间的首地址,其长度和可以执行的操作由其定义的数据类型决定。
一般来说,共用体的长度不仅包括其最大成员所需要的空间,还包括其尾部为了满足对齐要求而存在的必要填充(这部分不被使用)。因此只能说共用体所占内存长度不小于最大成员的长度,而不能断言共用体的长度等于最长成员的长度。
指针是一个地址的概念,有指向的概念,分为变量指针、函数指针、void指针。
如果一个变量专门用来存放地址,则称为指针变量。
使用“*”来表示这种指向关系。
使用“&”可以用来改变指针的指向。
变量的声明有两种情况,一种是需要建立存储空间的。例如,int在 声明的时候就己经建立了存储空间。这被称为“定义性声明(defining declaration) ”或者称 为“定义(definition) ”。另一种情况是不需要建立存储空间的,例如,extern int i中的变 量i是在别的文件中定义的,这被称为是“引用性声明(referencing declaration) ”。
此外,在定义指针时必须指定其类型,因为不同类型的变量在内存中所占据的空间是不 同的,这对于指针的移位操作具有很重要的意义。如果定义变量i是一个整型变量,且初值 为0,那么就是将i自加1对于上例中的指针类型p,如果有操作p++,那么对于指针 变量p自加1意味着什么呢?因为p是一个int型指针,所以p++操作意味着将指针p移动4 字节。由此可见,正确的定义指针类型是非常重要的。如果类型不匹配很可能得到错误的数 据或发生访问越界,这些都是非常危险的行为。
数据要存储到内存(可寄存器),才可以被访问。存储到内存时,可以存储为可读可写的形式,称为变量,其内存首地址就是变量名。
将指针运算和数组名a可以联系起来。但是两者毕竟存在区别,并不是所有 的运算都一样。特别地,可以通过指针运算来改变指针变量的值(也就是使指针向上或向下 移动)但是不能将同样的操作运用于a,即不存在a++之类的运算。只是因为a表示的是 数组首元素的地址,它是一个指针常景,所包含的值在程序运行时是不能被改变的。
通过指针来引用一个内存单元。
指针变量需要解引用。引用自动解引用。
值是内存内容的类型解释;
指针是一个复合类型,它由以下两个元素组成:
(A,T),A是内存物理地址,T是该地址包含的类型。
变量名实际上是一个以一个名字对应代表一个地址,在对程序编译连接时由编译系统给每一个变量名分配对应的内存地址。从变量中取值,实际上是通过变量名找到相应的内存地址,从该存储单元中读取数据。
指针的步长,不是字节,而是类型的长度;
C语言的三个函数malloc, calloc, realloc都是拥有很大风险的函数,在使用的时候务必记得对他们的结果进行校验,最好的办法还是对他们进行再包装,可以选择宏包装,也可以选择函数包装。
realloc函数是最为人诟病的一个函数,因为它的职能过于宽广,既能分配空间,也能释放空间,虽然看起来是一个好函数,但是有可能在不经意间会帮我们做一些意料之外的事情,例如多次释放空间。正确的做法就是,应该使用再包装阉割它的功能,使他只能进行扩展或者缩小堆内存块大小。
在内存分配的过程中,我们使用 malloc 进行分配,用 free 进行释放,但这是我们理解中的分配与释放吗? 在调用 malloc 时,该函数或使用 brk() 或使用 mmap() 向操作系统申请一片内存,在使用时分配给需要的地方,与之对应的是 free,与我们硬盘删除东西一样,实际上:
int* value = malloc(sizeof(int)*5); ... free(value); // 成对编码原则:写法malloc后,马上写free printf("%d\n", value[0]);
代码中,为什么在 free 之后,我又继续使用这个内存呢?因为 free 只是将该内存标记上释放的标记,示意分配内存的函数,我可以使用,但并没有破坏当前内存中的内容,直到有操作对它进行写入。 这便引申出几个问题:
Bug更加难以发现,让我们假设,如果我们有两个指针p1,p2指向同一个内存,如果我们对其中某一个指针使用了 free(p1); 操作,却忘记了还有另一个指针指向它,那这就会导致很严重的安全隐患,而且这个隐患十分难以发现,原因在于这个Bug并不会在当时显露出来,而是有可能在未来的某个时刻,不经意的让你的程序崩溃。
有可能会让某些问题更加简化,例如释放一个条条相连的链表域。
某些大哥提到说,free并不是什么都不做,而是将该段地址空间的前面一小部分置零 但是如果地址空间很长的话,依旧有误用的风险,希望大家还是警惕
实际上之所以库作者不让free操作将地址空间清空,有一部分原因是为了性能考虑,因为置零操作是一个消耗性能的行为,具体可以自行尝试,所谓双刃剑就在于此。
总的来说,还是那句话C语言是一把双刃剑。
变量声明向编译器保证变量以给定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义,在程序连接时编译器需要实际的变量声明。
当您使用多个文件且只在其中一个文件中定义变量时(定义变量的文件在程序连接时是可用的),变量声明就显得非常有用。您可以使用 extern 关键字在任何地方声明一个变量。虽然您可以在 C++ 程序中多次声明一个变量,但变量只能在某个文件、函数或代码块中被定义一次。
在函数或一个代码块(非控制结构的{}块,控制结构只是一条语句)内部声明的变量,称为局部变量。
一般情况下,系统为全局变量分配的存储空间在程序运行的过程中一般是不改变的,而为局部变量分配的存储单元是动态改变的。
变量是什么?是一个存储数据的内存单元,用变量名显式表示其内容值,变量包括右值和右值。其地址也可以称为指针,可以用一个指针变量来显式地引用地址值。
1 Accessing array elements. 数组名,如arr,需要使用下标为访问元素,不能使用arr++,而如果p = arr,可以使用p++。
2 Passing arguments to a function when the function needs to modify the original argument.
3 Passing arrays and strings to functions.
4 Obtaining memory from the system.
5 Creating data structures such as linked lists.
变量名是用来命名一个内存地址,这个地址可以是一块基本数据类型大小的首地址,也可以是一组相同类型的基本数据类型的首地址,也可以是一个事先自定义的结构体的首地址。
++p,++i,用于移动指针和下标,其实质是得到一个新的地址,只需++操作是因为这些内存单元是相信存储的;
两个指针相减,可以求出两个指针之间的存储单元大小;
调用realloc对一个内存块进行扩展,导致原来的内容发生了存储位置的变化, realloc函数既要调用free,又要调用malloc。执行时究竟调用哪个函数,取决于是要缩小还是扩大相应内存块的大小。
c编程语言中的每个变量必须在使用之前在声明部分声明。每个变量必须具有一个数据类型,用于确定要存储的值的范围和类型以及要分配的内存大小。
数组下标只能是常量表达式,编译器不能猜,可以推。
字面量可以有前缀和后缀;
不同数据类型的数据存储格式不同,所能实施的操作也不相同。
枚举型数据的输入和输出都是借助于整数来实现的。
如:SomeDigits digit=TWO;
cout<<digit; 则输出结果为2。
如: WEEKDAY d;
cin>>d;则要使d的值为Fri,应键入5。
一元操作符的结合性是从右向左,如*p++,两个符号的优先级是一样,结合性是从右向左,所以是先p++,然后*p
上面的说法不对,不管后面有没括号都是先计算*,因为后增的特殊性。
Traditional C function libraries provide reusability through predefined, precompiled functions, such as strlen() and rand(), that you can use in your program.
A data type is also defined in terms of the operations that can be performed on it. For example, the int type can use all the arithmetic operations. You can add, substract, multiply, and divide integers. You can also use the modulus opertor(%) with them.
The second method for representing floating-point values is called E notation, and it looks like this: 3.45E6. This means that the value 3.45 is multiplied by 1000000, the E6 means 10 to the 6th power,which is 1 followed by 6 zeros.
Pointers are variables that are designed to hold addresses.We say a pointer points to the address it holds.The pointer declaration always states to what type of object a pointer points.Applying the dereferencing operator (*) to a pointer yields the value at the locationto which the pointer points.
The value contained in memory where the deleted object resided is undefined, meaning the compiler can produce code that leaves anything there. In practice, major compilers will try to be as efficient as possible, so typically the object’s memory will remain untouched until the program reuses it for some other purposes. You would have to implement a custom destructor to, for example, zero out some sensitive contents.